#!/usr/bin/env python
"""
Axe Emulator.
Michael Lee
"""

import pygame
import numpy

class Emulator(object):
    ## Initialization ##
    def __init__(self, size=(96,64), pixel_size=3, caption="Axe Interpreter"):
        self.pixel_size = pixel_size
        self.size = size
        
        self._init_graphics(caption)        
        self._init_memory()
        return
    
    def _init_graphics(self, caption):
        pygame.init()
        self.screen = pygame.display.set_mode(self.size)
        pygame.display.set_caption(caption)
        
        self.WHITE = (255,255,255)
        self.LIGHT_GRAY = (170,170,170)
        self.GRAY = (128,128,128)
        self.DARK_GRAY = (85,85,85)
        self.BLACK = (0,0,0)
        
        self.clock = pygame.time.Clock()
        self.time = 0
        return
    
    def _init_memory(self):
        self._memory_size = 256 * 256 - 1
        self._memory = bytearray([0 for i in range(self._memory_size)])
        return
        
    ## Getters and setters ##
    def _fix_size(self, pair):
        return tuple([x * self.pixel_size for x in list(pair)])
    
    def _get_size(self):
        return self._size
    def _set_size(self, size):
        self._size = self._fix_size(size)
        self._buffer_size = ((self.size[0]//self.pixel_size) * 
                            (self.size[1]//self.pixel_size)) // 8
        return
    size = property(_get_size, _set_size)
    
    ## Variables ##
    def set_var(self, location, value, size=2):
        if size == 1:
            self._memory[location] = value
        else:
            self._memory[location] = value % 256
            self._memory[location + 1] = value // 256
        return
    
    def get_var(self, location, size):
        if size == 1:
            return self._memory[location]
        else:
            return self._memory[location] + self._memory[location + 1] * 256
        return
    
    def disp_screen(self, buffer1_loc, buffer2_loc=None, scale=2):
        buffer1 = self._memory[buffer1_loc:buffer1_loc + self._buffer_size]
        if buffer2_loc != None:
            buffer2 = self._memory[buffer2_loc:buffer2_loc + self._buffer_size]
        
        output = pygame.PixelArray(self.screen)
        
        mono = [self.WHITE, self.BLACK]
        three = [self.WHITE, self.GRAY, self.BLACK]
        four = [self.WHITE, self.LIGHT_GRAY, self.DARK_GRAY, self.BLACK]
        
        for i in range(self._buffer_size):
            for j in range(8):
                if scale == 2:
                    color = mono[self._get_bit2(buffer1[i], j)]
                if scale == 3:
                    color = three[(self._get_bit2(buffer1[i], j) * 2 + self._get_bit2(buffer2[i], j)) % 3]
                if scale == 4:
                    color = four[(self._get_bit2(buffer1[i], j) * 2 + self._get_bit2(buffer2[i], j)) % 4]
            
                actual_i = i * 8 + j
                x = (actual_i % (self.size[0]//self.pixel_size)) * self.pixel_size
                y = (actual_i // (self.size[0]//self.pixel_size)) * self.pixel_size
                for xt in range(self.pixel_size):
                    for yt in range(self.pixel_size):
                        output[x + xt][y + yt] = color
        pygame.display.flip()
        return
    
    def rect(self, loc, coords, size):
        draw_solid = lambda loc, index: self._set_bit(loc, index)
        self._generic_rect(loc, coords, size, draw_solid)
        return
    
    def inverse_rect(self, loc, coords, size):
        draw_inverse = lambda loc, index: self._flip_bit(loc, index)
        self._generic_rect(loc, coords, size, draw_inverse)
        return
    
    def _generic_rect(self, loc, coords, size, callback):
        x, y = coords
        w, h = size
        for j in range(y, y + h):
            for i in range(x, x + w):
                xt = i // 8
                yt = j * self.size[0] // self.pixel_size // 8
                callback(x + xt + yt, i % 8)
        return
    
    def and_sprite(self, loc, coords, size, data):
        def and_operation(loc, index, value):
            if value:
                self._set_bit(loc, index)
            else:
                self._clear_bit(loc, index)
            return
        self._generic_sprite(loc, coords, size, data, and_operation)
        return
    
    def _generic_sprite(self, loc, coords, size, data, callback):
        x, y = coords
        w, h = size
        count = 0
        for j in range(y, y + h):
            for i in range(x, x + w):
                xt = i // 8
                yt = j * self.size[0] // self.pixel_size // 8
                callback(x + xt + yt, i % 8, data[count])
                count += 1
        return
    
    def _get_bit(self, loc, index):
        return self._get_bit2(self._memory[loc], index)
    def _get_bit2(self, byte_value, index):
        return ((byte_value & (1 << index) ) != 0)
    def _set_bit(self, loc, offset):
        num = self._memory[loc]
        mask = 1 << offset
        self._memory[loc] = num | mask
        return
    def _clear_bit(self, loc, offset):
        num = self._memory[loc]
        mask = ~(1 << offset)
        self._memory[loc] = num & mask
        return
    def _flip_bit(loc, offset):
        num = self._memory[loc]
        mask = 1 << offset
        self._memory[loc] = num ^ mask
        return
        
